package controllers

import (
	"encoding/json"
	"github.com/gomodule/redigo/redis"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
	"nft-server/db"
	"nft-server/model"
	"nft-server/util"
	"regexp"
	"strconv"
	"sync"
	"time"
	"unicode/utf8"

	"nft-server/common"
	"nft-server/errcode"
	"nft-server/logger"
	"nft-server/service"
)

const (
	ApplyAssetURI        = "/v1/assets/apply"
	GetAssetURI          = "/v1/assets/get"
	VerifyAssetDetailURI = "/v1/assets/verify"
	GetAssetDetailURI    = "/v1/assets/detail"
	GetAssetListURI      = "/v1/assets/list"
	TransferAssetURI     = "/v1/assets/transfer"
	GetBlockchainURI     = "/v1/store/blocks/summary"
	GetTransactionsURI   = "/v1/transactions/list"
	GetBlockChainTypeURI = "/v1/blockchaintype"
)

const (
	usernameRegex   = "^[A-Za-z0-9]{1,12}$"
	genderRegex     = "(^male$)|(^female$)"
	collectionRegex = "^[A-Za-z][A-Za-z0-9]{3,32}"
	tokenIdRegex    = "^[0-9]{1,5}$"
	//assetIdRegex        = "(*)#[0-9]{1,5}$"
	transactionNumRegex = "^[1-9][0-9]{0,3}$"
	assetNumRegex       = "^[1-9][0-9]{0,1}$"

	trueOrFalseRegex    = "(^y$)|(^n$)"
	singleChoiceRegex   = "^[a-d]{1}$"
	multipleChoiceRegex = "^[a-f]{1,6}$"
	answerLength        = 300

	saveSurvey = false
	kafka      = false

	//redisSum			= "#redisSum"
	InvalidUserID = -1
)

type NftController struct {
	CommonController
}

var (
	lock sync.Mutex
)

//GetAssetDetail 获取某个具体藏品的详细信息
func (nc *NftController) GetAssetDetail() {
	req := common.AssetDetailReq{}
	req.UserId = cast.ToInt(nc.Ctx.Input.Query("userId"))
	req.CollectionIndex = cast.ToString(nc.Ctx.Input.Query("collectionIndex"))
	req.TokenId = cast.ToString(nc.Ctx.Input.Query("tokenId"))
	userId := cast.ToString(req.UserId)
	//校验userId
	if err := nc.CheckUserIdFromSession(req.UserId); err != nil {
		logger.Error(err.Error())
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage(err.Error()))
		return
	}
	//校验参数
	if len(req.CollectionIndex) == 0 || len(req.TokenId) == 0 {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("集合ID和tokenID均不可为空"))
		return
	}
	//确认藏品归属
	owner, err := service.GetAssetOwner(req.CollectionIndex, req.TokenId)
	if err != nil {
		logger.Error(err.Error())
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage(err.Error()))
		return
	}
	if owner.UserId != req.UserId {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("用户没有该藏品所有权"))
		return
	}
	response, err := service.BuildResponseDataForGetAssetDetail(userId, req)
	if err != nil || response == nil {
		logger.Error(err.Error())
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage(err.Error()))
		return
	}
	nc.HandleBusinessObj(response)
}

//CheckQualificationForApplyAsset 用于校验购买资格
func (nc *NftController) CheckQualificationForApplyAsset(req common.ApplyAssetReq) error {
	err := nc.CheckUserIdFromSession(req.UserId)
	if err != nil {
		logger.Error(err.Error())
		nc.HandleErrCode(errcode.E.Certification.HavaNotLogin)
		return err
	}
	err = service.JudgeAdminUser(nc.GetSession(common.SessionUserId).(int))
	if err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("管理员不能购买藏品"))
		return err
	}
	errorResponse := common.ApplyAssetErrorResponse{
		IdentityVerification: true,
		HaveLogin:            true,
	}
	//增加判断是否认证
	ok, err := service.FindAuthenticationInfo(req.UserId)
	if err != nil || ok == false {
		//剩余量小于0，则直接返回
		err = errors.Errorf("抱歉，未实名不能购买")
		errorResponse.IdentityVerification = false
		nc.HandleErrCodeAndData(errcode.E.Asset.Authorization.WithErr(err), errorResponse)
		return err
	}
	//查询集合是否存在
	collection, err := service.GetCollectionDetailById(req.CollectionIndex, req.SubCollectionIndex)
	if err != nil || collection == nil {
		logger.Error("GetCollectionDetailById error: %v", err)
		err = errors.Errorf("集合不存在")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithErr(err))
		return err
	}
	return nil
}
func (nc *NftController) CheckRedisForApplyAsset(userId int, req common.ApplyAssetReq, oldValue int) error {
	if oldValue <= 0 {
		//剩余量小于0，则直接返回
		err := errors.Errorf("抱歉，该藏品已领光")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage(err.Error()))
		return err
	}
	//判断是否处于发售时间--从缓存判断
	if service.CheckNowInSaleFromRedis(req.CollectionIndex, req.SubCollectionIndex, db.RedisPool) == false {
		err := errors.Errorf("抱歉，该藏品未处于发售时间段")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage(err.Error()))
		return err
	}
	//增加领取数量限制
	ok, err := service.CheckApplyNum(userId)
	if ok == false {
		err = errors.Errorf("抱歉，每个用户只能领取%v个藏品", *service.AvailableQuantity)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage(err.Error()))
		return err
	}
	return nil
}

//CreateOrderForApplyAsset 是生成订单的动作
func (nc *NftController) CreateOrderForApplyAsset(userId int, tokenId string, req common.ApplyAssetReq) (*model.Order, error) {
	//查询集合
	collection, err := service.GetCollectionDetailById(req.CollectionIndex, req.SubCollectionIndex)
	if err != nil {
		logger.Error("GetCollectionDetailById error: %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("集合不存在"))
		return nil, err
	}
	//创建订单
	order := service.CreateOrder(userId, req.CollectionIndex, req.SubCollectionIndex, tokenId, collection)
	//订单写入redis
	err = service.WriteOrderIntoRedis(order, db.RedisPool)
	if err != nil {
		_, _ = service.DeleteOrderFromRedis(userId, db.RedisPool)
		logger.Error("WriteOrderIntoRedis error: %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("系统创建订单失败"))
		return nil, err
	}
	//以下动作可以修改为消费者完成--TODO
	//订单写入db
	lock.Lock()
	err = service.SaveOrder(order)
	lock.Unlock()
	if err != nil {
		logger.Error("SaveOrder error: %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("系统创建订单失败"))
		return nil, err
	}
	return order, nil
}

//ApplyAsset 购买藏品
func (nc *NftController) ApplyAsset() {
	//获取请求头
	req := common.ApplyAssetReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("ApplyAssetReq Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Collection.Create.WithErr(err))
		return
	}
	err := nc.CheckQualificationForApplyAsset(req)
	if err != nil {
		return
	}
	conn := db.RedisPool.Get()
	defer conn.Close()
	//判断缓存中是否已存在该用户订单,已存在则直接返回该订单
	order, err := service.GetOrderFromRedis(req.UserId, db.RedisPool)
	if err == nil {
		nowTime := time.Now()
		if order.OrderStatus != util.TradeNoPay || order.ExpireTime.Before(nowTime) == true {
			_, _ = service.DealWithOrder(order.OrderId, util.TradeClosed, nil)
			//关闭超时订单
		} else {
			nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("您存在尚未支付的订单"))
			return
		}
	}
	response := common.OrderResponse{}
	//缓存中不存在则直接创建订单
	//集合在redis中的key
	keyOfCollection := service.BuildKeyOfSubCollection(req.CollectionIndex, cast.ToString(req.SubCollectionIndex))
	//获取该集合中token数量
	oldTokenNum, _ := redis.Int(conn.Do("SCARD", keyOfCollection))
	err = nc.CheckRedisForApplyAsset(req.UserId, req, oldTokenNum)
	if err != nil {
		return
	}
	//redis加锁--锁定库存
	lock.Lock()
	//从集合中随机删除并返回一个tokenID
	tokenId, err := redis.String(conn.Do("SPOP", keyOfCollection))
	lock.Unlock()
	if err != nil {
		logger.Error("redis error: counter: %v,err: %v", tokenId, err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("获取藏品失败"))
		return
	}
	order, err = nc.CreateOrderForApplyAsset(req.UserId, tokenId, req)
	if err != nil || order == nil {
		return
	}
	response = common.OrderResponse{OrderId: order.OrderId, CollectionIndex: order.CollectionIndex, TokenId: order.TokenId, OrderStatus: order.OrderStatus, CreatedTime: util.GetMilliTime(order.CreatedTime), ExpiredTime: util.GetMilliTime(order.ExpireTime), Currency: order.Currency, Price: order.Price}
	//领取接口返回系统订单
	nc.HandleBusinessObj(response)
	return
}

// TransferPresentAssets 转赠
func (nc *NftController) TransferPresentAssets() {
	req := common.TransferPresentAssetReq{}
	if err := json.Unmarshal(nc.Ctx.Input.RequestBody, &req); err != nil {
		logger.Error("TransferPresentAssets Unmarshal error: %v", err)
		nc.HandleErrCode(errcode.E.Collection.Create.WithErr(err))
		return
	}
	assetInfo, err := service.GetAssetByAssetId(req.AssetId)
	if err != nil {
		logger.Error("TransferPresentAssets Get Asset By AssetId error: %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("获取藏品信息失败"))
		return
	}
	if assetInfo == nil {
		logger.Error("TransferPresentAssets Asset not found: %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("没有找到该藏品相关信息"))
		return
	}
	if req.UserId != cast.ToInt(assetInfo.Owner) {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("您还未拥有该藏品"))
		return
	}
	//转赠方是否有权限
	sendUserInfo, rcvUserInfo, err := nc.checkSenderAndReceiverCanTransfer(req.UserId, req.ReceiverAddress)
	if err != nil {
		return //已处理
	}
	collectionIndex, tokenId := service.GetCollectionIndexAndId(assetInfo.IdWithCollectionIndex)

	_, err = service.TransferAssets(collectionIndex, tokenId,
		rcvUserInfo.UserId, req.ReceiverAddress, sendUserInfo)

	if err != nil {
		logger.Error("Fail to transfer assets:  %v", err)
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("无法完成转赠，请联系管理员"))
		return
	}
	nc.HandleBusinessSuccess()
}
func (nc *NftController) checkSenderAndReceiverCanTransfer(senderUserId int, receiverAddress string) (sendUserInfo *model.User, rcvUserInfo *common.User, err error) {
	sendUserInfo, err = service.GetUserAllInfoById(senderUserId)
	if err != nil {
		logger.Error("TransferPresentAssets error: fail to find user by user id ")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("无法查找到用户，请联系管理员"))
		return nil, nil, errcode.E.Base.ErrorMessage.WithMessage("无法查找到用户，请联系管理员")
	}
	if sendUserInfo == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("无法查找到用户，请联系管理员"))
		return nil, nil, errcode.E.Base.ErrorMessage.WithMessage("无法查找到用户，请联系管理员")
	}
	if !sendUserInfo.Authenticated {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("转赠方未实名认证，无法转赠"))
		return nil, nil, errcode.E.Base.ErrorMessage.WithMessage("转赠方未实名认证，无法转赠")
	}
	//接收方信息
	rcvUserInfo, err = service.GetUserInfoByAddress(receiverAddress)
	if err != nil {
		logger.Error("TransferPresentAssets error: fail to find user by user address")
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("无法查找到用户，请联系管理员"))
		return nil, nil, errcode.E.Base.ErrorMessage.WithMessage("无法查找到用户，请联系管理员")
	}
	if rcvUserInfo == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("地址填写有误,接收方信息不存在"))
		return nil, nil, errcode.E.Base.ErrorMessage.WithMessage("地址填写有误,接收方信息不存在")
	}
	if !rcvUserInfo.Authenticated {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("接收方未实名认证，无法转赠"))
		return nil, nil, errcode.E.Base.ErrorMessage.WithMessage("接收方未实名认证，无法转赠")
	}
	if rcvUserInfo.UserId == sendUserInfo.Id {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("不能给自己转赠"))
		return nil, nil, errcode.E.Base.ErrorMessage.WithMessage("不能给自己转赠")
	}
	return sendUserInfo, rcvUserInfo, nil
}

// GetAssets 获取当前用户所有NFT
func (nc *NftController) GetAssets() {
	//获取请求头
	req := common.GetAssetReq{}
	req.UserId = cast.ToInt(nc.Ctx.Input.Query("userId"))
	req.Page = cast.ToInt(nc.Ctx.Input.Query("page"))
	req.PageSize = cast.ToInt(nc.Ctx.Input.Query("pageSize"))

	if req.Page <= 0 || req.PageSize <= 0 {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("页码和页面大小必须大于0"))
		return
	}
	if err := nc.CheckUserIdFromSession(req.UserId); err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("无效用户ID"))
		return
	}

	assets, size, err := service.GetAssets(req.UserId, req.Page, req.PageSize)
	if err != nil {
		logger.Error("GetAssets error: %v,", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage(errcode.AssetGetFail))
		return
	}
	AssetAllResponse := common.AssetAllResponse{}
	AssetAllResponse.Assets = assets
	AssetAllResponse.TotalNum = size

	nc.HandleBusinessObj(AssetAllResponse)
}

//VerifyAssetDetail 查询指定Id的NFT的具体信息用于核验
func (nc *NftController) VerifyAssetDetail() {
	assetId := nc.Ctx.Input.Query("id")

	collectionIndex, tokenId := service.GetCollectionIndexAndId(assetId)
	logger.Debug("collectionIndex: %v, tokenId: %v", collectionIndex, tokenId)

	asset, err := service.GetAsset(collectionIndex, tokenId)
	if err != nil {
		logger.Error("GetAsset error: %v, collectionIndex: %v, tokenId: %v", err, collectionIndex, tokenId)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage(errcode.AssetDetailGetFail))
		return
	}

	if asset == nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessage("查询的资产不存在"))
		return
	}

	assetDetail, err := service.GetAssetDetail(asset)
	if err != nil {
		logger.Error("GetAssetDetail error: %v, collectionIndex: %v, tokenId: %v", err, collectionIndex, tokenId)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage(errcode.AssetDetailGetFail))
		return
	}
	nc.HandleBusinessObj(assetDetail)
}

func (nc *NftController) GetBlockchain() {
	summary, err := service.GetBlockchain()
	if err != nil {
		logger.Error("GetBlockchain error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage(errcode.BlockSummaryGetFail))
		return
	}
	nc.HandleBusinessObj(summary)
}

func (nc *NftController) GetTransactions() {
	transactionNum := nc.Ctx.Input.Query("number")

	if isMatch, err := regexp.MatchString(transactionNumRegex, transactionNum); !isMatch || err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessagef("无效的交易数量:[%v]", transactionNum))
		return
	}

	num, err := strconv.ParseInt(transactionNum, 10, 64)
	if err != nil {
		logger.Error("Parse transaction number error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}

	transactions, err := service.GetTransactions(int(num))
	if err != nil {
		logger.Error("GetTransactions error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage(errcode.TransactionsGetFail))
		return
	}
	nc.HandleBusinessObj(transactions)
}

//首页画廊
func (nc *NftController) GetAssetsList() {
	assetsNum := nc.Ctx.Input.Query("number")
	if isMatch, err := regexp.MatchString(assetNumRegex, assetsNum); !isMatch || err != nil {
		nc.HandleErrCode(errcode.E.Base.ErrorMessage.WithMessagef("invalid asset number [%v]", assetsNum))
		return
	}

	num, err := strconv.ParseInt(assetsNum, 10, 64)
	if err != nil {
		logger.Error("Parse asset number error: %v", err)
		nc.HandleErrCode(errcode.E.Base.RequestInvalid.WithErr(err))
		return
	}
	assetList, err := service.GetAssetList(int(num))
	if err != nil {
		logger.Error("GetAssetList error: %v", err)
		nc.HandleErrCode(errcode.E.Internal.Internal.WithMessage(errcode.AssetListGetFail))
		return
	}
	nc.HandleBusinessObj(assetList)
}

func (nc *NftController) GetBlockChainType() {

	if common.BlockChainType == common.BlockChainSharedMode {
		blockChainType := common.BlockChainTypeResponse{Type: common.BlockChainSharedMode}
		nc.HandleBusinessObj(blockChainType)
		return
	}
	blockChainType := common.BlockChainTypeResponse{Type: common.BlockChainPrivateMode}
	nc.HandleBusinessObj(blockChainType)
	return
}

func validateSurvey(survey common.Survey) error {
	if isMatch, err := regexp.MatchString(trueOrFalseRegex, survey.Q1); !isMatch || err != nil {
		return errors.Errorf("invalid Q1 answer [%v]", survey.Q1)
	}
	if isMatch, err := regexp.MatchString(trueOrFalseRegex, survey.Q2); !isMatch || err != nil {
		return errors.Errorf("invalid Q2 answer [%v]", survey.Q2)
	}
	if isMatch, err := regexp.MatchString(multipleChoiceRegex, survey.Q3); !isMatch || err != nil {
		return errors.Errorf("invalid Q3 answer [%v]", survey.Q3)
	}
	if isMatch, err := regexp.MatchString(singleChoiceRegex, survey.Q4); !isMatch || err != nil {
		return errors.Errorf("invalid Q4 answer [%v]", survey.Q4)
	}
	if isMatch, err := regexp.MatchString(singleChoiceRegex, survey.Q5); !isMatch || err != nil {
		return errors.Errorf("invalid Q5 answer [%v]", survey.Q5)
	}
	if isMatch, err := regexp.MatchString(singleChoiceRegex, survey.Q6); !isMatch || err != nil {
		return errors.Errorf("invalid Q6 answer [%v]", survey.Q6)
	}
	if utf8.RuneCountInString(survey.Q7) > answerLength {
		return errors.Errorf("Q7 answer too long")
	}
	if isMatch, err := regexp.MatchString(trueOrFalseRegex, survey.Q8); !isMatch || err != nil {
		return errors.Errorf("invalid Q8 answer [%v]", survey.Q8)
	}
	return nil
}
